﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using JBeamLibrary.Tools;

namespace DriveBeamEditor.Enums
{
    public enum NumericMaskTypes
    {
        Bit,
        SByte,
        Byte,
        UShort,
        Short,
        UInt,
        Int,
        ULong,
        Long,
        Float,
        Double,
        Decimal
    }

    public static class NumericMasks
    {
        #region Constants

        public static readonly Dictionary<NumericMaskTypes, Type> TypeMap = new Dictionary<NumericMaskTypes, Type>(11)
        {
            { NumericMaskTypes.Bit, typeof(ushort) },
            { NumericMaskTypes.SByte, typeof(sbyte) },
            { NumericMaskTypes.Byte, typeof(byte) },
            { NumericMaskTypes.UShort, typeof(ushort) },
            { NumericMaskTypes.Short, typeof(short) },
            { NumericMaskTypes.UInt, typeof(uint) },
            { NumericMaskTypes.Int, typeof(int) },
            { NumericMaskTypes.ULong, typeof(ulong) },
            { NumericMaskTypes.Long, typeof(long) },
            { NumericMaskTypes.Float, typeof(float) },
            { NumericMaskTypes.Double, typeof(double) },
            { NumericMaskTypes.Decimal, typeof(decimal) }
        };

        public static readonly Dictionary<NumericMaskTypes, object> DefaultsMap = new Dictionary<NumericMaskTypes, object>(11)
        {
            { NumericMaskTypes.Bit, default(ushort) },
            { NumericMaskTypes.SByte, default(sbyte) },
            { NumericMaskTypes.Byte, default(byte) },
            { NumericMaskTypes.UShort, default(ushort) },
            { NumericMaskTypes.Short, default(short) },
            { NumericMaskTypes.UInt, default(uint) },
            { NumericMaskTypes.Int, default(int) },
            { NumericMaskTypes.ULong, default(ulong) },
            { NumericMaskTypes.Long, default(long) },
            { NumericMaskTypes.Float, default(float) },
            { NumericMaskTypes.Double, default(double) },
            { NumericMaskTypes.Decimal, default(decimal) }
        };

        public static readonly Dictionary<NumericMaskTypes, string> RegExMap = new Dictionary<NumericMaskTypes, string>(11)
        {
            { NumericMaskTypes.Bit, @"^0$|^1$" },
            { NumericMaskTypes.SByte, @"^0$|^-?[1-9][0-9]{0,2}$" },
            { NumericMaskTypes.Byte, @"^0$|^[1-9][0-9]{0,2}$" },
            { NumericMaskTypes.UShort, @"^0$|^[1-9][0-9]{0,4}$" },
            { NumericMaskTypes.Short, @"^0$|^-?[1-9][0-9]{0,4}$" },
            { NumericMaskTypes.UInt, @"^0$|^[1-9][0-9]{0,9}$" },
            { NumericMaskTypes.Int, @"^0$|^-?[1-9][0-9]{0,9}$" },
            { NumericMaskTypes.ULong, @"^0$|^[1-9][0-9]{0,19}$" },
            { NumericMaskTypes.Long, @"^0$|^-?[1-9][0-9]{0,18}$" },
            { NumericMaskTypes.Float, @"^-?0\.[0-9]{1,3}$|^-?[1-9][0-9]{0,5}\.[0-9]{1,3}$" },
            { NumericMaskTypes.Double, @"^-?0\.[0-9]{1,3}$|^-?[1-9][0-9]{0,10}\.[0-9]{1,3}$" },
            { NumericMaskTypes.Decimal, @"^-?0\.[0-9]{1,2}$|^-?[1-9][0-9]{0,10}\.[0-9]{1,2}$" }
        };

        public static readonly Dictionary<NumericMaskTypes, KeyValuePair<object, object>> RangeMap = new Dictionary<NumericMaskTypes, KeyValuePair<object, object>>(11)
        {
            { NumericMaskTypes.Bit, new KeyValuePair<object, object>(0, 1) },
            { NumericMaskTypes.SByte, new KeyValuePair<object, object>(sbyte.MinValue, sbyte.MaxValue) },
            { NumericMaskTypes.Byte, new KeyValuePair<object, object>(byte.MinValue, byte.MaxValue) },
            { NumericMaskTypes.UShort, new KeyValuePair<object, object>(ushort.MinValue, ushort.MaxValue) },
            { NumericMaskTypes.Short, new KeyValuePair<object, object>(short.MinValue, short.MaxValue) },
            { NumericMaskTypes.UInt, new KeyValuePair<object, object>(uint.MinValue, uint.MaxValue) },
            { NumericMaskTypes.Int, new KeyValuePair<object, object>(int.MinValue, int.MaxValue) },
            { NumericMaskTypes.ULong, new KeyValuePair<object, object>(ulong.MinValue, ulong.MaxValue) },
            { NumericMaskTypes.Long, new KeyValuePair<object, object>(long.MinValue, long.MaxValue) },
            { NumericMaskTypes.Float, new KeyValuePair<object, object>(float.MinValue, float.MaxValue) },
            { NumericMaskTypes.Double, new KeyValuePair<object, object>(double.MinValue, double.MaxValue) },
            { NumericMaskTypes.Decimal, new KeyValuePair<object, object>(decimal.MinValue, decimal.MaxValue) }
        };


        #endregion

        #region Methods

        public static NumericMaskTypes GetMaskTypeFromType(Type type)
        {
            return TypeMap.First(pair => pair.Value == type).Key;
        }

        public static string GetNumericString(NumericMaskTypes maskType, object value)
        {
            switch (maskType)
            {
                case NumericMaskTypes.Bit:
                case NumericMaskTypes.SByte:
                case NumericMaskTypes.Byte:
                case NumericMaskTypes.UShort:
                case NumericMaskTypes.Short:
                case NumericMaskTypes.UInt:
                case NumericMaskTypes.Int:
                case NumericMaskTypes.ULong:
                case NumericMaskTypes.Long:
                    return value.ToString().Replace(".", "");
                case NumericMaskTypes.Float:
                case NumericMaskTypes.Double:
                case NumericMaskTypes.Decimal:
                    var str = value.ToString().Replace(",", ".");
                    if (!str.Contains("."))
                        str += ".0";
                    return str;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public static bool ValidateValue(NumericMaskTypes maskType, object baseValue, out object newValue)
        {
            /* Max value */
            if (baseValue.ToStringOrEmpty().ToUpperInvariant().Equals("MAX"))
            {
                newValue = RangeMap[maskType].Value;
                return true;
            }

            /* Null value */
            if (baseValue.ToStringOrEmpty().ToUpperInvariant().Equals("NULL"))
            {
                newValue = RangeMap[maskType].Key;
                return true;
            }

            newValue = null;
            try
            {
                /* Try to convert given value to the mask type's value type */
                newValue = Convert.ChangeType(baseValue, TypeMap[maskType], NumberFormatInfo.InvariantInfo);

                /* Check that mask type's regex matches */
                return newValue != null && Regex.IsMatch(GetNumericString(maskType, newValue), RegExMap[maskType]);
            }
            catch (InvalidCastException)
            {
                newValue = DefaultsMap[maskType];
                return false;
            }
            catch (FormatException)
            {
                newValue = DefaultsMap[maskType];
                return false;
            }
            catch (OverflowException)
            {
                newValue = DefaultsMap[maskType];
                return false;
            }
            catch (ArgumentNullException)
            {
                newValue = DefaultsMap[maskType];
                return false;
            }
        }

        #endregion
    }
}
